package org.iabako.server.serviceimpl.business;

import org.apache.log4j.Logger;
import org.iabako.server.dao.JpaDAO;
import org.iabako.server.dao.business.QueryDefinitionDAO;
import org.iabako.server.dao.business.QuoteDAO;
import org.iabako.server.dao.business.SaleDAO;
import org.iabako.shared.entity.business.Sale;
import org.iabako.shared.entity.business.SaleProductService;
import org.iabako.shared.entity.enumeration.FinancialStatusSale;
import org.iabako.shared.entity.enumeration.TrackingType;
import org.iabako.shared.entity.user.Enterprise;
import org.iabako.shared.exception.IabakoActionForbiddenException;
import org.iabako.shared.exception.IabakoStockException;
import org.iabako.shared.exception.IabakoUniqueConstraintException;
import org.iabako.shared.iservice.business.QuoteService;
import org.iabako.shared.iservice.business.SaleService;
import org.iabako.shared.iservice.business.TrackingService;
import org.iabako.shared.querydefinition.SearchMotorQueryEnum;
import org.iabako.shared.querydefinition.columndefinition.SaleColumnEnum;
import org.iabako.shared.querydefinition.dto.QueryDefinitionDTO;
import org.iabako.shared.querydefinition.dto.QueryOperatorEnum;
import org.iabako.shared.tools.GenericTools;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by jose on 12/07/15.
 */

@org.springframework.stereotype.Service("quoteService")
public class QuoteServiceImpl extends AbstractServiceImpl implements QuoteService {

    static Logger log = Logger.getLogger(SaleServiceImpl.class.getName());

    @Autowired
    private SaleDAO saleDAO;
    @Autowired
    private QuoteDAO quoteDAO;
    @Autowired
    private SaleService saleService;
    @Autowired
    private QueryDefinitionDAO queryDefinitionDAO;
    @Autowired
    private TrackingService trackingService;

    @Override
    protected JpaDAO getDAO() {
        return quoteDAO;
    }

    public QueryDefinitionDTO getSearchQueryDefinitionDTO(QueryDefinitionDTO qd) {
        qd.setSearchMotorQuery(SearchMotorQueryEnum.QuoteSearch);
        qd.addCriteria(SaleColumnEnum.saleQuote, QueryOperatorEnum.EQUALS, true);
        qd.addCriteria(SaleColumnEnum.saleEnterprise, QueryOperatorEnum.IN, (ArrayList) getSessionUser().getEnterprise().getVisibleEnterprisesFinancialData());
        queryDefinitionDAO.executeQueryDefinition(qd);
        qd.setCustomModifications(true);

        return qd;
    }

    public QueryDefinitionDTO setSearchCriteriaFromSuggest(QueryDefinitionDTO qd, Long hiddenSelectionCode, String suggestionValue) {
        return saleService.setSearchCriteriaFromSuggest(qd, hiddenSelectionCode, suggestionValue);
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public Boolean remove(Long id) {
        saleDAO.deleteAttachedObjects(id);

        Sale sale = quoteDAO.findById(id);

        boolean result = super.remove(id);

        trackingService.addTrackingToUserSession(TrackingType.quoteDelete, sale);

        return result;
    }

    /**
     * We can't pass by cascading persist of SaleProductServices because GWT does not support (serialize)
     * PersistentBag and Proxy/Lazy hibernate objects
     *
     * @param sale Sale object from GWT client (navigator/JS world)
     */
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public synchronized Sale save(Sale sale, List<SaleProductService> productsServices)
            throws IabakoActionForbiddenException,
            IabakoUniqueConstraintException {

        Enterprise enterprise = getEnterpriseFromSessionUser();

        if (enterprise != null && enterprise.isDemo()) {
            throw new IabakoActionForbiddenException("action_forbidden_for_demo_title", "action_forbidden_for_demo_text", true);
        }

        if (!getDAO().isUniqueNumber(sale)) {
            throw new IabakoUniqueConstraintException(
                    messages.getLabel("validation_sale_unique_constraint_error_title"),
                    messages.getLabel("validation_sale_unique_constraint_error_text", sale.getNumber()), true, true);
        }

        if (sale.getEnterprise() == null) {
            sale.setEnterprise(enterprise);
        }

        if (sale.getClient() != null && sale.getClient().getEnterprise() == null) {
            sale.getClient().setEnterprise(enterprise);
        }

        sale.setQuote(true);
        sale.setDate(GenericTools.getDateFromString(sale.getDateString()));

        //By default, only draft and generated quotes are considered as changed
        boolean quoteHasChanged = sale.getStatus() != FinancialStatusSale._2_quote_transformed && sale.getStatus() != FinancialStatusSale._3_quote_discard;

        Sale quoteFromDB = null;
        if (sale.getId() != null && quoteHasChanged) {
            quoteFromDB = saleDAO.findById(sale.getId());
            //We check if quote has really changed
            quoteHasChanged = quoteHasChanged(sale, quoteFromDB) || productServiceHasChanged(sale, quoteFromDB, productsServices);
        }
        calculateStatus(sale, quoteFromDB, quoteHasChanged);


        addProductsServicesToSale(sale, productsServices);

        sale.setRecalculatedTotal(sale.calculateAmountFromProductsServices());
        sale.setTaxPercent(sale.getPonderedTaxPercent());

        sale = (Sale) super.save(sale);

        setBusinessTrackInfo(sale);

        if (quoteHasChanged && sale.getStatus() == FinancialStatusSale._0_quote_draft) {
            trackingService.addTrackingToUserSession(TrackingType.quoteDraft, sale);
        }

        return sale;
    }

    private void calculateStatus(Sale quoteToMerge, Sale quoteFromDB, boolean hasChanged) {
        if (quoteToMerge.getStatus() == FinancialStatusSale._2_quote_transformed || quoteToMerge.getStatus() == FinancialStatusSale._3_quote_discard) {
            return;
        }

        if (quoteToMerge.getId() == null && quoteToMerge.getStatus() == FinancialStatusSale._1_quote_generated) {
            //Creation to be generated by InvoiceGeneratorService
            return;
        }

        if (quoteFromDB != null && quoteToMerge.getStatus() == FinancialStatusSale._1_quote_generated && quoteFromDB.getStatus() == FinancialStatusSale._0_quote_draft) {
            //Status has been updated by InvoiceGeneratorService
            return;
        }

        if (quoteToMerge.getStatus() == FinancialStatusSale._0_quote_draft && quoteFromDB.getStatus() == FinancialStatusSale._3_quote_discard) {
            //Status has been updated by recover action
            return;
        }

        if (quoteToMerge.getId() == null || hasChanged) {
            quoteToMerge.setStatus(FinancialStatusSale._0_quote_draft);
        } else {
            //If there's no change, get status from DB
            quoteToMerge.setStatus(quoteFromDB.getStatus());
        }
    }

    private boolean productServiceHasChanged(Sale quoteToMerge, Sale quoteFromDB, List<SaleProductService> productServiceList) {
        if ((quoteFromDB.getClient() == null && quoteToMerge.getClient() != null)
                || (quoteToMerge.getClient() == null && quoteFromDB.getClient() != null)
                || (quoteFromDB.getClient() != null && !quoteFromDB.getClient().equals(quoteToMerge.getClient()))) {
            return true;
        }
        if (quoteFromDB.getProductsServices().size() != productServiceList.size()) {
            return true;
        }
        for (SaleProductService saleProductService : quoteFromDB.getProductsServices()) {
            if (!productServiceList.contains(saleProductService)) {
                return true;
            }
        }
        for (SaleProductService saleProductService : productServiceList) {
            if (!quoteFromDB.getProductsServices().contains(saleProductService)) {
                return true;
            }
        }
        return false;
    }

    private boolean quoteHasChanged(Sale quoteToMerge, Sale quoteFromDB) {
        if ((quoteFromDB.getClient() == null && quoteToMerge.getClient() != null)
                || (quoteToMerge.getClient() == null && quoteFromDB.getClient() != null)
                || (quoteFromDB.getClient() != null && !quoteFromDB.getClient().equals(quoteToMerge.getClient()))) {
            return true;
        }
        if ((quoteFromDB.getComment() == null && quoteToMerge.getComment() != null)
                || (quoteToMerge.getComment() == null && quoteFromDB.getComment() != null)
                || (quoteFromDB.getComment() != null && !quoteFromDB.getComment().equals(quoteToMerge.getComment()))) {
            return true;
        }
        boolean numberHasChanged = !quoteToMerge.getNumber().equals(quoteFromDB.getNumber());
        boolean dateHasChanged = !quoteToMerge.getDate().equals(quoteFromDB.getDate());

        return numberHasChanged || dateHasChanged;
    }

    private void addProductsServicesToSale(Sale saleFromDB, List<SaleProductService> productsServices) {
        try {
            saleService.addProductsServicesToSale(true, saleFromDB, productsServices);
        } catch (IabakoStockException e) {
            //This should never happen because of draft = true !
            log.error(e.getMessage(), e);
        }
    }
}
